package com.archive.cleaner;

import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import com.alibaba.druid.pool.DruidDataSource;
import com.archive.cleaner.config.DbCleanerConfig;
import com.archive.cleaner.constants.CleanMode;
import com.archive.cleaner.processor.CleanProcessor;
import com.archive.cleaner.util.CleanerUtil;
import com.archive.core.ArchiveContext;
import com.archive.core.config.ArchiveProperties;
import com.archive.core.util.Assert;
import com.archive.core.util.SystemClock;
import org.apache.commons.cli.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.File;
import java.sql.SQLException;

public class Main {

    public static final String AppName = "db-cleaner";

    static {
        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
        JoranConfigurator configurator = new JoranConfigurator();
        configurator.setContext(lc);
        lc.reset();
        String logBackConfig = System.getProperty("log_back_config");
        if (logBackConfig == null || logBackConfig.trim().isEmpty()) {
            System.out.println("Exception: can not find log config.");
            System.exit(1);
        }
        try {
            configurator.doConfigure(new File(logBackConfig));
        } catch (JoranException e) {
            System.out.println("Exception: " + e.getMessage());
            System.exit(1);
        }
    }

    public static void main(String[] args) {
        PosixParser parser = new PosixParser();
        HelpFormatter hf = new HelpFormatter();
        hf.setWidth(110);

        Logger logger = LoggerFactory.getLogger(AppName);

        Main main = new Main();
        Options options = main.createOptions();
        DruidDataSource dataSource = null;
        try {
            CommandLine commandLine = parser.parse(options, args);
            if (commandLine.hasOption('h')) {
                hf.printHelp(AppName, options, true);
                System.exit(0);
            } else if (commandLine.hasOption('c')) {
                String file = commandLine.getOptionValue('c');
                DbCleanerConfig dbCleanerConfig = CleanerUtil.parseConfig(file);
                ArchiveProperties archiveProperties = dbCleanerConfig.getArchive();

                main.checkConfig(dbCleanerConfig);

                dataSource = main.createDataSource(dbCleanerConfig);

                CleanProcessor cleanProcessor = main.createCleanProcessor(logger, dataSource);
                if (commandLine.hasOption('l')) {
                    cleanProcessor.listTimestamp();
                    return;
                }

                if (commandLine.hasOption('t')) {
                    String tableSuffix = commandLine.getOptionValue('t');
                    cleanProcessor.listTablesWithSuffix(tableSuffix);
                    return;
                }

                // 清理模式保存清理时间戳，设置备份表后缀
                CleanMode cleanMode = dbCleanerConfig.getCleanMode();
                if (CleanMode.CLEAN == cleanMode) {
                    long now = SystemClock.now();
                    String tableSuffix = "_c" + now;
                    archiveProperties.setArchiveTableSuffix(tableSuffix);

                    cleanProcessor.saveBackupTimeStamp(now, tableSuffix, dataSource);
                } else {
                    Assert.hasText(archiveProperties.getArchiveTableSuffix(), () -> "archiveTableSuffix can not be null.");
                }

                ArchiveContext archiveContext = new ArchiveContext();
                archiveContext.setDataSource(dataSource);
                archiveContext.startArchive(archiveProperties);

                if (CleanMode.CLEAN == cleanMode) {
                    if (dbCleanerConfig.isDeleteEnabled()) {
                        cleanProcessor.clean(dbCleanerConfig.getDeleteInterval());
                    } else {
                        logger.info("delete disabled.");
                    }
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        } finally {
            if (dataSource != null) {
                dataSource.close();
            }
        }
    }

    private DruidDataSource createDataSource(DbCleanerConfig dbCleanerConfig) throws SQLException {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl(dbCleanerConfig.getUrl());
        dataSource.setUsername(dbCleanerConfig.getUsername());
        dataSource.setPassword(dbCleanerConfig.getPassword());
        dataSource.setInitialSize(5);
        dataSource.setMaxActive(5);
        dataSource.setConnectTimeout(5000);
        dataSource.setSocketTimeout(60000);
        dataSource.setTestWhileIdle(true);
        dataSource.setValidationQuery("select '1' from dual");
        dataSource.setMaxWait(60000);

        dataSource.init();

        return dataSource;
    }

    private CleanProcessor createCleanProcessor(Logger logger, DataSource dataSource) {
        CleanProcessor cleanProcessor = new CleanProcessor(logger);
        cleanProcessor.setDataSource(dataSource);
        cleanProcessor.setLogger(logger);
        return cleanProcessor;
    }

    private void checkConfig(DbCleanerConfig dbCleanerConfig) {
        Assert.notNull(dbCleanerConfig.getUsername(), () -> "database username can not be null.");
        Assert.notNull(dbCleanerConfig.getPassword(), () -> "database password can not be null.");
        Assert.notNull(dbCleanerConfig.getUsername(), () -> "database url can not be null.");
        if (dbCleanerConfig.isDeleteEnabled()) {
            Integer deleteInterval = dbCleanerConfig.getDeleteInterval();
            Assert.isTrue(deleteInterval != null && deleteInterval > 0, () -> "deleteInterval must be greater than zero.");
        }
    }

    public Options createOptions() {
        Options options = new Options();

        Option opt = new Option("h", "help", false, "Print help");
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option("c", "config", true, "Specify the config file path");
        opt.setRequired(true);
        options.addOption(opt);

        opt = new Option("l", "list", false, "Show the backup timestamp list");
        opt.setRequired(false);
        options.addOption(opt);

        opt = new Option("t", "tables", true, "Show the backup tables with specified timestamp suffix");
        opt.setRequired(false);
        options.addOption(opt);

        return options;
    }

}
